﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace AppGene.DataModel.Generators
{
    internal class DatabaseBuilder
    {
        /// <summary>
        /// Build database from a specific data model assembly.
        /// </summary>
        /// <param name="database">The database instance.</param>
        /// <param name="assembly">The data model assembly.</param>
        public static void BuildFromAssembly(Database database,
                                        Assembly assembly)
        {
            BuildTables(database, assembly);
            BuildRelations(database, assembly);
        }

        /// <summary>
        /// Build tables from a specific data model assembly into a database.
        /// </summary>
        private static void BuildTables(Database database,
                                        Assembly assembly)
        {
            IEnumerable<Type> AllTableTypes = GetTables(assembly);
            foreach (Type item in AllTableTypes)
            {
                BuildTable(item);
            }
        }

        /// <summary>
        /// Build a table instance from specific type.
        /// </summary>
        /// <param name="tableType"></param>
        /// <returns></returns>
        private static LogicalTable BuildTable(Type tableType)
        {
            LogicalTable table = BuildTableBaseInfo(tableType);

            BuildTableColumns(table, tableType);
            BuildPrimaryKey(table, tableType);
            BuildIndexes(table, tableType);

            return table;
        }

        private static void BuildIndexes(LogicalTable table, Type tableType)
        {
            throw new NotImplementedException();
        }

        private static void BuildPrimaryKey(LogicalTable table, Type tableType)
        {
            LogicalPrimaryKeyAttribute primaryKey = tableType.GetCustomAttribute<LogicalPrimaryKeyAttribute>();
            if (primaryKey != null)
            {
                //table.;
            }
        }

        /// <summary>
        /// Build a table instance with table basic information.
        /// </summary>
        /// <param name="tableType"></param>
        /// <returns></returns>
        private static LogicalTable BuildTableBaseInfo(Type tableType)
        {
            LogicalTableAttribute tableAttribute = tableType.GetCustomAttribute<LogicalTableAttribute>();
            if (tableAttribute != null)
            {
                LogicalTable table = new LogicalTable(tableAttribute.Name);
                table.Description = tableAttribute.Description;
                return table;
            }

            return new LogicalTable(tableType.Name);
        }

        private static void BuildTableColumns(LogicalTable table,
            Type tableType)
        {
            PropertyInfo[] properties = tableType.GetProperties();
            foreach (var property in properties)
            {
                BuildTableColumn(table, property);
            }
        }

        private static void BuildTableColumn(LogicalTable table,
            PropertyInfo property)
        {
            LogicalColumnAttribute columnAttribute = property.GetCustomAttribute<LogicalColumnAttribute>();
            string columnName = GetColumnName(property, columnAttribute);
            DbType? columnDataType = GetColumnDataType(property, columnAttribute);
            string columnNativeDataType = GetColumnNativeDataType(property, columnAttribute);
            LogicalColumn column = null;
            if (columnDataType.HasValue)
            {
                column = table.AddColumn(columnName, columnDataType.Value);
            }
            else
            {
                column = table.AddColumn(columnName, columnNativeDataType);
            }

            if (columnAttribute != null)
            {
                column.AllowNull = columnAttribute.AllowNull;
                column.DefaultValue = columnAttribute.DefaultValue;
                column.Description = columnAttribute.Description;
                column.IdentityIncrement = columnAttribute.IdentityIncrement;
                column.IdentitySeed = columnAttribute.IdentitySeed;
                column.IsIdentity = columnAttribute.IsIdentity;
                column.Label = columnAttribute.Label;
                column.Length = columnAttribute.Length;
                column.Options = columnAttribute.Options;
                column.Precision = columnAttribute.Precision;
                column.Scale = columnAttribute.Scale;
                // TODO: How to support column order in a table.
            }
        }

        /// <summary>
        /// Gets column name.
        /// </summary>
        /// <param name="property">The column property info.</param>
        /// <param name="columnAttribute">The column attribute.</param>
        /// <returns>The column name.</returns>
        private static string GetColumnName(PropertyInfo property,
            LogicalColumnAttribute columnAttribute)
        {
            return (columnAttribute == null
                        || string.IsNullOrEmpty(columnAttribute.Name))
                    ? property.Name
                    : columnAttribute.Name;
        }

        /// <summary>
        /// Gets column data type.
        /// </summary>
        /// <param name="property">The column property info.</param>
        /// <param name="columnAttribute">The column attribute.</param>
        /// <returns>The column data type.</returns>
        private static DbType? GetColumnDataType(PropertyInfo property,
            LogicalColumnAttribute columnAttribute)
        {
            if (columnAttribute == null)
            {
                return GetColumnDataType(property);
            }
            else if (!string.IsNullOrEmpty(columnAttribute.NativeDataType))
            {
                return null;
            }
            return columnAttribute.DataType;
        }

        /// <summary>
        /// Gets column data type.
        /// </summary>
        /// <param name="property">The column property info.</param>
        /// <returns>The column data type.</returns>
        private static DbType GetColumnDataType(PropertyInfo property)
        {
            return LogicalDataTypeConvertor.ToDbType(property.PropertyType);
        }

        /// <summary>
        /// Gets column native data type.
        /// </summary>
        /// <param name="property">The column property info.</param>
        /// <param name="columnAttribute">The column attribute.</param>
        /// <returns>The column data type.</returns>
        private static string GetColumnNativeDataType(PropertyInfo property,
            LogicalColumnAttribute columnAttribute)
        {
            return columnAttribute.NativeDataType;
        }

        /// <summary>
        /// Build relations (foreign keys) from a specific data model assembly into a database.
        /// </summary>
        /// <param name="database"></param>
        /// <param name="assembly"></param>
        private static void BuildRelations(Database database,
                                        Assembly assembly)
        {

        }

        /// <summary>
        /// Get tables' classes.
        /// </summary>
        /// <param name="assembly"></param>
        /// <returns></returns>
        private static IEnumerable<Type> GetTables(Assembly assembly)
        {
            return from type in assembly.GetTypes()
                   where type.IsClass
                   select type;
        }
    }
}
